package utils

import (
	"errors"
	"mygolang/models"
	"net/http"
	"strings"
	"time"

	"github.com/beego/beego/v2/server/web/context"
	jwt "github.com/dgrijalva/jwt-go"
)

const (
	HeaderAuth      = "Authorization"
	Bearer          = "Bearer "
	TokenCookieName = "app-jwtoken"
	LoginURL        = "/login"
	ExpiresAt       = 15000
	AppSecret       = "app-secret-abc"
)

func CreateToken(loginInfo *models.LoginModel) string {
	claims := models.TokenClaims{
		Username: loginInfo.Username,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: ExpiresAt,
			Issuer:    "this-app",
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	signedToken, _ := token.SignedString([]byte(AppSecret))

	return signedToken
}

// ValidateTokenFilter validate token
func ValidateTokenFilter(ctx *context.Context) {
	url := ctx.Request.URL.String()
	if strings.Contains(url, LoginURL) {
		return
	}
	token := getTokenFromContext(ctx)
	if token == "" {
		http.Redirect(ctx.ResponseWriter, ctx.Request, LoginURL, http.StatusUnauthorized)
	}
	username, err := validateToken(token)
	if err != nil {
		http.Redirect(ctx.ResponseWriter, ctx.Request, LoginURL, http.StatusUnauthorized)
	}
	ctx.Input.SetData("username", username)
}

// getTokenFromContext get token from request
func getTokenFromContext(ctx *context.Context) string {
	token := ""
	authStr := ctx.Input.Header(HeaderAuth)

	if authStr != "" && strings.Index(authStr, Bearer) >= 0 {
		token = authStr[len(Bearer):]
	} else {
		token = ctx.GetCookie(TokenCookieName)
	}

	return token
}

func validateToken(jwtFromHeader string) (string, error) {
	token, err := jwt.ParseWithClaims(
		jwtFromHeader,
		&models.TokenClaims{},
		func(token *jwt.Token) (interface{}, error) {
			return []byte(AppSecret), nil
		},
	)

	if err != nil {
		return "", errors.New("couldn't parse claims")
	}

	claims, ok := token.Claims.(*models.TokenClaims)
	if !ok {
		return "", errors.New("couldn't parse claims")
	}

	if claims.ExpiresAt < time.Now().UTC().Unix() {
		return "", errors.New("jwt is expired")
	}

	username := claims.Username
	return username, nil
}
